// scalaxx - Scala XML eXtensions
// (c) 2009, Normen Müller, normen.mueller@gmail.com
// $Id: Lexer.scala 50 2009-11-12 11:37:31Z normen.mueller $
package scalaxx.path

import scala.util.parsing.syntax._
import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [path] trait XPathTokens extends Tokens {
  trait FilterTok extends Token  
  trait NameTok extends Token  
  case object AllTok extends NameTok with FilterTok { def chars = "*" }
  case class NCTok(local: String) extends NameTok with FilterTok { def chars = local }
  case class QTok(prefix: String, local: String) extends NameTok with FilterTok { def chars = prefix+":"+local }
  case class TyTok(name: String) extends FilterTok { def chars = name }
  case class FunTok(name: NameTok) extends Token with FilterTok { def chars = name.chars }
  case class AxisTok(name: String, filter: FilterTok) extends Token { def chars = name+"::"+filter }
  case class OpTok(name: String) extends Token { def chars = name }
  case class VarRefTok(name: NameTok) extends Token { def chars = "$"+name }
}

/**
 * @todo PI with parameters
 * @todo Whitespaces may be freely added within patterns before or after any Token.
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
//@todo Is a Lexer with RegexParser a proper approach? Just for my understanding: How to w/o RegexParser?
private [path] class Lexer extends StdLexical with RegexParsers with XPathTokens {
  override type Elem = Char
  
  override def token =
    ( EofCh ^^^ EOF
    | tfexp | ax | lit | num | opn | idexp | op | vr
    | '\'' ~> failure("unclosed string literal")
    | '\"' ~> failure("unclosed string literal")
    | delim
    | failure("illegal character")
    )
  
  lazy val tf  =
    ( ("comment" | "text" | "processing-instruction" | "node") <~ '(' <~ ')' ^^ {case t => TyTok(t)}
    | qn <~ '('                                                              ^^ {case n => FunTok(n)}
    )
  
  lazy val ax  =
    ( "ancestor-or-self" | "ancestor" | "attribute" | "child" | "descendant-or-self" | "descendant"
    | "following-sibling" | "following" | "namespace" | "parent" | "preceding-sibling"
    | "preceding" | "self"
    ) ~ "::" ~ (tf | id) ^^ {case a~_~i => AxisTok(a,i)}
  
  lazy val id = "*" ^^^ AllTok | nc <~ ":*" ^^ {case NCTok(n) => QTok(n, "*")} | qn
  
  lazy val idexp  =
    ( "*"       ^^^ AxisTok("child", AllTok)
    | nc <~ ":*" ^^ {case NCTok(n) => AxisTok("child", QTok(n, "*"))}
    | qn         ^^ {case n        => AxisTok("child", n)}
    )
  
  lazy val tfexp  =
    ( ("comment" | "text" | "processing-instruction" | "node") <~ '(' <~ ')' ^^ {case t => AxisTok("child", TyTok(t))}
    | qn <~ '('                                                              ^^ {case n => FunTok(n)}
    )
  
  lazy val vr  = '$' ~> qn ^^ {case n => VarRefTok(n)}
  
  def op  = ("*" | "/" | "|" | "+" | "-" | "=" | "!=" | "<=" | "<" | ">=" | ">") ^^ {case s => OpTok(s)}
  
  lazy val opn =
    ( whitespace ~> "-" <~ whitespace
    | whitespace ~> "and" <~ whitespace
    | whitespace ~> "or" <~ whitespace
    | whitespace ~> "mod" <~ whitespace
    | whitespace ~> "div" <~ whitespace
    ) ^^ {case s => OpTok(s)}
  
  lazy val qn  = opt(nc <~ ':') ~ nc ^^ {
    case Some(NCTok(p)) ~ NCTok(l) => QTok(p,l)
    case None ~ i                  => i
  }
  
  lazy val nc  = (letter | digit | '_') ~ rep(letter | digit | '.' | '_' | '-') ^^ {case first ~ rest => NCTok(first :: rest mkString "")}
  //def num = digit ~ rep(digit) ^^ {case first ~ rest => NumericLit(first :: rest mkString "")}
  
  lazy val lit =
    ( '\'' ~ rep(chrExcept('\'', '\n', EofCh)) ~ '\'' ^^ {case '\'' ~ chars ~ '\'' => StringLit(chars mkString "")}
    | '\"' ~ rep(chrExcept('\"', '\n', EofCh)) ~ '\"' ^^ {case '\"' ~ chars ~ '\"' => StringLit(chars mkString "")}
    )
  
  override def letter = elem("letter", ch => ch.isLetter | ch == '-')
  
  override def whitespace = rep(whitespaceChar)
  
  // copied form scala.util.parsing.json.Lexer
  def num = intPart ~ opt(fracPart) ~ opt(expPart) ^^ {case i ~ f ~ e => NumericLit(i + optString(".", f) + optString("", e))}
  def intPart = zero | intList
  def intList = nonzero ~ rep(digit) ^^ {case x ~ y => (x :: y) mkString ""}
  def fracPart = '.' ~> rep(digit) ^^ { _ mkString "" }
  def expPart = exponent ~ opt(sign) ~ rep1(digit) ^^ {case e ~ s ~ d => e + optString("", s) + d.mkString("")}
  def zero: Parser[String] = '0' ^^^ "0"
  def nonzero = elem("nonzero digit", d => d.isDigit && d != '0')
  def exponent = elem("exponent character", d => d == 'e' || d == 'E')
  def sign = elem("sign character", d => d == '-' || d == '+')
  private def optString[A](pre: String, a: Option[A]) = a match {case Some(x) => pre + x.toString case None => ""}
}
